package com.ruoyi.web.controller;

import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
import cn.hutool.http.server.HttpServerResponse;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.dyun.tool.ssecurity.utils.YunSecurityUtils;
import com.ruoyi.common.constant.CommonConstants;
import com.ruoyi.common.constant.UserConstants;
import com.ruoyi.common.entity.ValidateData;
import com.ruoyi.common.enums.BaseStatusEnum;
import com.ruoyi.common.enums.SocialCodeEnum;
import com.ruoyi.common.exception.CustomException;
import com.ruoyi.framework.config.AlismsConfig;
import com.ruoyi.framework.sms.ValidateCodeTemplate;
import com.ruoyi.framework.utils.SmsUtils;
import com.ruoyi.framework.web.controller.BaseController;
import com.ruoyi.project.biz.entity.BizUser;
import com.ruoyi.project.biz.entity.WxUser;
import com.ruoyi.project.biz.service.IBizUserService;
import com.ruoyi.project.biz.service.WxUserService;
import com.ruoyi.project.setting.security.LoginBody;
import com.ruoyi.project.setting.security.LoginUser;
import com.ruoyi.project.setting.security.service.SysLoginService;
import com.ruoyi.project.setting.security.service.TokenService;
import com.ruoyi.project.setting.security.utils.UserUtils;
import com.ruoyi.project.system.entity.SysUser;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.bean.WxOAuth2UserInfo;
import me.chanjar.weixin.common.bean.oauth2.WxOAuth2AccessToken;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.mp.api.WxMpService;
import net.dreamlu.mica.core.result.R;
import net.dreamlu.mica.core.utils.$;
import net.dreamlu.mica.core.utils.RandomType;
import net.dreamlu.mica.core.utils.WebUtil;
import net.dreamlu.mica.launch.MicaProperties;
import net.dreamlu.mica.redis.ratelimiter.RateLimiter;
import net.dreamlu.mica.redis.ratelimiter.RateLimiterClient;
import org.springframework.util.ObjectUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletResponse;
import javax.validation.constraints.NotBlank;

/**
 * 登陆 控制层
 *
 * @author dy
 */
@Validated
@Slf4j
@RestController
@AllArgsConstructor
public class LoginController extends BaseController {

    private final MicaProperties micaProperties;

    private final SysLoginService loginService;

    private final TokenService tokenService;

    private final IBizUserService userService;

    private final WxMpService wxMpService;

    private final WxMaService wxMaService;

    private final WxUserService wxUserService;

    private final RateLimiterClient rateLimiterClient;

    /**
     * sms, 1小时不得超过5次
     */
    @GetMapping("/captcha/sms")
    @RateLimiter(value = "smsCode", param = "#phone", max = 5, ttl = 60)
    public R sendMessage(@NotBlank(message = "手机号不能为空") String phone) {
        if (!SmsUtils.checkPhoneNum(phone)) {
            return fail("手机号码不正确");
        }

        // 生成验证码
        ValidateData validateData = SmsUtils.generateValidateData(phone);

        // 生成内容
        ValidateCodeTemplate validateCodeTemplate = new ValidateCodeTemplate(phone);

        // 测试环境不发送短信了，麻烦。
        boolean notProd = !"prod".equals(micaProperties.getEnv());
        if (notProd) {
            return success(validateData);
        }

        // 发送内容,自营直接发送已签名即可。
        boolean flag = AlismsConfig.sendSignMessage(validateCodeTemplate.getSmsRequest(validateData));
        if (flag) {
            validateData.setCode("");
            return success(validateData);
        }
        return fail("短信获取失败");
    }

    /**
     * 用户登陆，所有平台用户都需根据手机号进行唯一标识。不想写字段openid，暂用username字段
     */
    @PostMapping("/login")
    public R login(@Validated @RequestBody LoginBody loginBody) {
        if (ObjectUtils.isEmpty(loginBody.getPhone())) {
            return fail("手机号不能为空");
        }
        // 密码登录
        if (!ObjectUtils.isEmpty(loginBody.getPassword())) {
            BizUser user = userService.getOne(new LambdaUpdateWrapper<BizUser>()
                    .eq(BizUser::getPhone, loginBody.getPhone()));
            if (user == null || !YunSecurityUtils.getPasswordEncoder().matches(loginBody.getPassword(), user.getPassword())) {
                throw new CustomException("用户不存在/密码错误");
            }
            if (BaseStatusEnum.DB_STATUS_DISABLE.getStatus().equals(user.getStatus())) {
                throw new CustomException("该用户已被停用");
            }
            // 因平台同一采用手机号登录，用户名暂用为openid
            boolean passed = bindWxUser(loginBody.getUsername(), user);
            if (!passed) {
                return fail("该账户已绑定其他微信号");
            }
            return doLogin(user);
        }
        // 验证码登录
        if (ObjectUtils.isEmpty(loginBody.getCode())) {
            return fail("验证码不能为空");
        }
        boolean validated = SmsUtils.validate(loginBody.getPhone(), loginBody.getCode());
        if (!validated) {
            return fail("验证码错误");
        }
        BizUser bizUser = userService.getOne(new LambdaQueryWrapper<BizUser>().eq(BizUser::getPhone, loginBody.getPhone()));
        if (bizUser == null) {
            bizUser = createUser();
            bizUser.setPhone(loginBody.getPhone());
            userService.save(bizUser);
        }
        boolean passed = bindWxUser(loginBody.getUsername(), bizUser);
        if (!passed) {
            return fail("该账户已绑定其他微信号");
        }

        return doLogin(bizUser);
    }

    /**
     * 默认注册
     */
    private BizUser createUser() {
        BizUser bizUser = new BizUser();
        String userName = "RBZ_" + $.random(6, RandomType.ALL);
        bizUser.setUserName(userName);
        bizUser.setNickName(userName);
        bizUser.setPassword($.random(32));
        bizUser.setAvatar("https://iconfont.alicdn.com/t/56a9e618-bebe-4105-9e47-ac5bcc2062b2.png");
        bizUser.setSocialCode(UserConstants.FRONTEND);
        bizUser.setStatus(BaseStatusEnum.DB_STATUS_NORMAL.getStatus());
        return bizUser;
    }

    /**
     * 执行登录
     */
    private R doLogin(BizUser bizUser) {
        if (BaseStatusEnum.DB_STATUS_DISABLE.getStatus().equals(bizUser.getStatus())) {
            throw new CustomException("该用户已被停用");
        }
        SysUser sysUser = $.copy(bizUser, SysUser.class);
        return success(loginService.login(sysUser));
    }

    /**
     * 用户退出
     */
    @PostMapping("/weblogout")
    public R logout() {
        try {
            LoginUser loginUser = tokenService.currentLoginUser();
            // 删除用户缓存记录
            tokenService.delLoginUser(loginUser.getToken());
            SysLoginService.recordLoginLog(loginUser, BaseStatusEnum.DB_STATUS_SUCCESS.getStatus(), "退出成功");
        } catch (Exception e) {
            log.error(e.getMessage());
            return success();
        }
        return success();
    }

    /**
     * 获取用户信息
     *
     * @return 用户信息
     */
    @GetMapping("/getInfo")
    public R getInfo() {
        // 更新角色权限信息, 不及时更新可返回success(tokenService.getLoginUser().toLoginUserVO)
        LoginUser loginUserOld = tokenService.currentLoginUser();
        BizUser bizUser = userService.getById(loginUserOld.getUserId());
        SysUser sysUser = $.copy(bizUser, SysUser.class);
        sysUser.setExtension(loginUserOld.getSysUser().getExtension());
        return success(loginService.initLoginUser(loginUserOld.getToken(), sysUser).toLoginUserVO());
    }

    /**
     * 用户登陆
     */
    @PostMapping("/updateInfo")
    public R updateInfo(@Validated @RequestBody LoginBody loginBody) {
        if (ObjectUtils.isEmpty(loginBody.getPhone())) {
            // 修改密码及修改手机号都需要填写手机号
            return fail("手机号不能为空");
        }
        // 该手机需要接收到验证码
        if (ObjectUtils.isEmpty(loginBody.getCode())) {
            return fail("验证码不能为空");
        }
        boolean validated = SmsUtils.validate(loginBody.getUuid(), loginBody.getCode());
        if (!validated) {
            return fail("验证码错误");
        }
        BizUser user = userService.getById(UserUtils.currentLoginUser().getUserId());
        // 密码和手机号同时存在时，优先修改密码
        // 修改密码
        if (!ObjectUtils.isEmpty(loginBody.getPassword())) {
            user.setPassword(YunSecurityUtils.getPasswordEncoder().encode(loginBody.getPassword()));
            userService.updateById(user);
            return success();
        }
        // 修改或绑定手机号
        if (!ObjectUtils.isEmpty(loginBody.getPhone())) {
            BizUser bizUser = userService.getOne(new LambdaQueryWrapper<BizUser>().eq(BizUser::getPhone, loginBody.getPhone()));
            if (bizUser != null && !bizUser.getId().equals(user.getId())) {
                // 该手机号已绑定用户
                return fail("该手机号已绑定用户");
            }
            user.setPhone(loginBody.getPhone());
            userService.updateById(user);
            return success();
        }
        return fail();
    }

    /**
     * 微信授权登录，标准：不影响主平台登录逻辑
     */
    @RequestMapping("/login/snsapi_userinfo")
    public R getUserBySnsApiUserInfo(@RequestParam String code) {
        WxOAuth2UserInfo mpUser;
        /*
            由于access_token拥有较短的有效期，当access_token超时后，
            可以使用refresh_token进行刷新，refresh_token有效期为30天，当refresh_token失效之后，需要用户重新授权。
            在本地存储方案有的情况下，access_token起的作用仅仅是刚注册时获取用户信息。网页授权access_token接口无访问限制，无需缓存。
         */
        try {
            WxOAuth2AccessToken accessToken = wxMpService.getOAuth2Service().getAccessToken(code);
            // 当授权为snsapi_userinfo时需要继续请求
            mpUser = wxMpService.getOAuth2Service().getUserInfo(accessToken, null);
        } catch (WxErrorException e) {
            return fail("微信授权登录失败");
        }
        WxUser wxUser = new WxUser();
        wxUser.setIsFrom(SocialCodeEnum.WECHAT_MP.getCode());
        wxUser.setOpenid(mpUser.getOpenid());
        wxUser.setNickname(mpUser.getNickname());
        wxUser.setSex($.toStr(mpUser.getSex()));
        wxUser.setPrivilege(mpUser.getProvince());
        wxUser.setCity(mpUser.getCity());
        wxUser.setCountry(mpUser.getCountry());
        wxUser.setHeadimgurl(mpUser.getHeadImgUrl());
        wxUser.setUnionid(mpUser.getUnionId());
        return getUser(wxUser);
    }

    /**
     * 微信授权登录，标准：不影响主平台登录逻辑
     */
    @RequestMapping("/login/snsapi_base")
    public R getUserBySnsApiUserBase(@RequestParam String code) {
        WxOAuth2AccessToken oAuth2AccessToken;
        try {
            oAuth2AccessToken = wxMpService.getOAuth2Service().getAccessToken(code);
        } catch (WxErrorException e) {
            return fail("微信授权登录失败");
        }
        WxUser wxUser = new WxUser();
        wxUser.setIsFrom(SocialCodeEnum.WECHAT_MP.getCode());
        wxUser.setOpenid(oAuth2AccessToken.getOpenId());
        wxUser.setUnionid(oAuth2AccessToken.getUnionId());
        return getUser(wxUser);
    }

    /**
     * 返回平台或微信用户
     *
     * @param wxUser 微信用户
     * @return 平台或微信用户
     */
    public R getUser(WxUser wxUser) {
        BizUser bizUser;
        // 用户mpUser查询微信用户表。如果没有该openid，那么插入一条数据，执行账号登录流程。有数据未绑定用户，执行账号登录流程。有账号有绑定，直接登录。
        WxUser oldwxUser = wxUserService.getOne(new LambdaQueryWrapper<WxUser>().eq(WxUser::getOpenid, wxUser.getOpenid()));
        if (!ObjectUtils.isEmpty(oldwxUser) && !ObjectUtils.isEmpty(oldwxUser.getBizUserId())) {
            bizUser = userService.getById(oldwxUser.getBizUserId());
            if (bizUser != null) {
                bizUser.getExtension().put(CommonConstants.OPENID, wxUser.getOpenid());
                // 存在直接返回,返回的是登录对象
                return doLogin(bizUser);
            }
        }
        // openid入库
        wxUserService.saveOrUpdate(wxUser, new LambdaUpdateWrapper<WxUser>().eq(WxUser::getOpenid, wxUser.getOpenid()));
        // 返回的是微信对象
        return success(wxUser);
    }

    public boolean bindWxUser(String openid, BizUser bizUser) {
        // 存在openid表示该微信用户未绑定平台账号，如果已绑定将会直接登录
        if (!ObjectUtils.isEmpty(openid)) {
            WxUser tmpWxUser = wxUserService.getOne(new LambdaQueryWrapper<WxUser>().eq(WxUser::getOpenid, openid));
            if (tmpWxUser != null) {
                // 该用户是否已绑定过微信
                WxUser bizUserHasOpenid = wxUserService.getOne(new LambdaUpdateWrapper<WxUser>()
                        .eq(WxUser::getIsFrom, tmpWxUser.getIsFrom())
                        .eq(WxUser::getBizUserId, bizUser.getId()));
                // 已经绑定过，如果此时放任通过，届时将导致逻辑混乱
                if (bizUserHasOpenid != null) {
                    return false;
                }
                WxUser wxUser = new WxUser();
                wxUser.setBizUserId(bizUser.getId());
                wxUserService.update(wxUser, new LambdaUpdateWrapper<WxUser>().eq(WxUser::getOpenid, openid));

                bizUser.getExtension().put(CommonConstants.OPENID, openid);
                return true;
            }
        }
        return true;
    }

    /**
     * 微信小程序/APP登录，标准：不影响主平台登录逻辑
     */
    @RequestMapping("/login/code2session")
    public R getUserBySnsApiUserBaseMa(String code, WxMaJscode2SessionResult sessionInfo) {
        WxUser wxUser = new WxUser();
        wxUser.setIsFrom(SocialCodeEnum.WECHAT_APP.getCode());
        try {
            if ($.isNotBlank(code)) {
                // 有code表示小程序登录
                wxUser.setIsFrom(SocialCodeEnum.WECHAT_MA.getCode());
                sessionInfo = wxMaService.getUserService().getSessionInfo(code);
            }
        } catch (WxErrorException e) {
            return fail("授权登录失败");
        }
        if ($.isNotBlank(sessionInfo.getOpenid())) {
            wxUser.setOpenid(sessionInfo.getOpenid());
            wxUser.setUnionid(sessionInfo.getUnionid());
            return getUser(wxUser);
        }
        return fail("授权登录失败");
    }
}